﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace StyleMVVM.DotNet.Aspects.Impl
{
	public class AspectClassManager
	{
		private static volatile AspectClassManager instance;
		private static object lockObject = new object();
		private ModuleBuilder moduleBuilder;
		private AssemblyBuilder assemblyBuilder;
		private ConcurrentDictionary<Type, Type> augmentedTypes;
		private ConcurrentBag<Tuple<string, Type, int, object>> globalAspects;
		private ConcurrentDictionary<Type, Tuple<string, Type, int, object>> individualAspects;

		public static AspectClassManager Instance
		{
			get
			{
				if (instance == null)
				{
					lock (lockObject)
					{
						if (instance == null)
						{
							instance = new AspectClassManager();
						}
					}
				}
				
				return instance;
			}
			set
			{
				if (value == null)
				{
					instance = null;
				}
			}
		}

		public AspectClassManager()
		{
			augmentedTypes = new ConcurrentDictionary<Type, Type>();
			globalAspects = new ConcurrentBag<Tuple<string, Type,int, object>>();
			individualAspects = new ConcurrentDictionary<Type, Tuple<string, Type,int, object>>();
			
			AppDomain myCurrentDomain = AppDomain.CurrentDomain;
			AssemblyName myAssemblyName = new AssemblyName();
			myAssemblyName.Name = "TempAssembly.dll";

			assemblyBuilder = myCurrentDomain.DefineDynamicAssembly
				(myAssemblyName, AssemblyBuilderAccess.RunAndSave);

			// Define a dynamic module in this assembly.
			moduleBuilder = assemblyBuilder.
				DefineDynamicModule("TempModule", "TempAssembly.dll");
		
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="memberName">name of the member to attribute or null for all</param>
		/// <param name="aspect">aspect to apply</param>
		/// <param name="priority"></param>
		/// <param name="initialization"></param>
		public void AddGlobalAspect(string memberName, Type aspect, int priority, object initialization)
		{

		}

		public void AddAspect(Type aspectedType, string memberName, Type aspectType, int priority, object initialization)
		{

		}

		/// <summary>
		/// Gets an aspected type, called by type augmenter
		/// </summary>
		/// <param name="inType"></param>
		/// <returns></returns>
		public Type GetAspectedType(Type inType)
		{
			Type returnValue;

			if (!augmentedTypes.TryGetValue(inType, out returnValue))
			{
				AspectClassInfo classInfo = InspectTypeForAugmentation(inType);

				if (classInfo != null)
				{
					AspectClassCreator classCreator = new AspectClassCreator(moduleBuilder, classInfo);

					returnValue = classCreator.CreateType();

					assemblyBuilder.Save("TempAssembly.dll");
				}
				else
				{
					returnValue = inType;
				}

				augmentedTypes[inType] = returnValue;
			}

			return returnValue;
		}

		private AspectClassInfo InspectTypeForAugmentation(Type inType)
		{
			bool produceProxy = globalAspects.Count > 0;
			List<IAspectTypeProviderAttribute> globalAttrs = new List<IAspectTypeProviderAttribute>();

			foreach (object customAttribute in inType.GetCustomAttributes(true))
			{
				IAspectTypeProviderAttribute aspectTypeProviderAttribute = customAttribute as IAspectTypeProviderAttribute;

				if (aspectTypeProviderAttribute != null)
				{
					produceProxy = true;
					globalAttrs.Add(aspectTypeProviderAttribute);
				}
				else if (customAttribute is EnableAspectsAttribute)
				{
					produceProxy = true;
				}
			}

			if (produceProxy)
			{
				AspectClassInfo returnValue = new AspectClassInfo { ClassType = inType };

				foreach (MethodInfo methodInfo in inType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
				{
					if (methodInfo.IsVirtual)
					{
						foreach (Tuple<string, Type, int, object> globalAspect in globalAspects)
						{
							returnValue.Aspects.Add(new AspectInfo
								                        {
																	MemberInfo =  methodInfo,
									                        AspectPriority = globalAspect.Item3,
																	AspectType = globalAspect.Item2
								                        });
						}

						foreach (Attribute customAttribute in methodInfo.GetCustomAttributes(true))
						{
							IAspectTypeProviderAttribute aspectTypeProvider = customAttribute as IAspectTypeProviderAttribute;

							if (aspectTypeProvider != null)
							{
								Type aspectType;
								IDictionary<string, object> init;
								int priority;

								aspectTypeProvider.ProvideAspectInfo(inType,out aspectType,out init,out priority);

								returnValue.Aspects.Add(new AspectInfo
									                        {
										                        MemberInfo = methodInfo,
																		AspectType = aspectType,
																		AspectInitalizeValues = init,
																		AspectPriority = priority
									                        });
							}
						}
					}
				}

				foreach (PropertyInfo propertyInfo in inType.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
				{
					if (propertyInfo.GetMethod.IsVirtual)
					{

					}
				}

				if (returnValue.Aspects.Count > 0)
				{
					return returnValue;
				}
			}

			return null;
		}
	}
}
